package cn.pugle.httpcomponent;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

public class ClientUtils {

    private static Logger logger = LoggerFactory.getLogger(ClientUtils.class);

    public static CloseableHttpClient createHttpClient(final int retryCount, int maxCon, int maxConPerRoute) {

        // Trust own CA and all self-signed certs
        SSLContext sslcontext;
        try {
            sslcontext = SSLContexts.custom()
                    .loadTrustMaterial(KeyStore.getInstance(KeyStore.getDefaultType()), new TrustSelfSignedStrategy())
                    .build();
        } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
            logger.error("Error while create sslConetxt", e);
            return null;
        }
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);

        // Create a registry of custom connection socket factories for supported
        // protocol schemes.
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE).register("https", sslsf).build();

        // Create a connection manager with custom configuration.
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry,
                new ManagedHttpClientConnectionFactory(), new SystemDefaultDnsResolver());
        // Configure total max or per route limits for persistent connections
        // that can be kept in the pool or leased by the connection manager.
        maxCon = maxCon == 0 ? 20 : maxCon;
        maxConPerRoute = maxConPerRoute == 0 ? 10 : maxConPerRoute;
        connManager.setMaxTotal(maxCon);
        connManager.setDefaultMaxPerRoute(maxConPerRoute);

        HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {

            @Override
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                if (executionCount >= retryCount) {
                    // Do not retry if over max retry count
                    return false;
                }
                if (exception instanceof ConnectTimeoutException) {
                    // Connection refused
                    return false;
                }
                if (exception instanceof SocketTimeoutException) {
                    return false;
                }
                if (exception instanceof UnknownHostException) {
                    // Unknown host
                    return false;
                }
                if (exception instanceof SSLException) {
                    // SSL handshake exception
                    return false;
                }
                /*if (exception instanceof InterruptedIOException) {
                    // Timeout
                    return true;
                }*/
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
                if (idempotent) {
                    // Retry if the request is considered idempotent
                    logger.info("Retry request " + exception.getMessage() + " " + request.getRequestLine());
                    return true;
                }
                return false;
            }
        };

        CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(connManager)
                .setRetryHandler(retryHandler).build();
        return httpclient;
    }

    public static HttpRequestBase configRequest(HttpRequestBase req, Map<String, String> headerMap) {
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(2000)
                .setConnectTimeout(2000)
                .setConnectionRequestTimeout(2000).build();
        req.setConfig(requestConfig);
        Header[] headers = new Header[headerMap.size()];
        int index = 0;
        for (Iterator<Entry<String, String>> it = headerMap.entrySet().iterator(); it.hasNext(); ) {
            Entry<String, String> next = it.next();
            headers[index++] = new BasicHeader(next.getKey(), next.getValue());
        }
        req.setHeaders(headers);
        return req;
    }

    public static String execute(HttpClient httpClient, HttpRequestBase req) throws IOException {
        // Create a custom response handler
        final String uri = req.getRequestLine().getUri();
        ResponseHandler<String> responseHandler = new ResponseHandler<String>() {

            public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {
                int status = response.getStatusLine().getStatusCode();
                String result = "";
                HttpEntity entity = response.getEntity();
                if (null != entity) {
                    String charset = entity.getContentEncoding() == null ? "" : entity.getContentEncoding().getValue();
                    charset = StringUtils.isNotBlank(charset) ? charset : "UTF-8";
                    result = EntityUtils.toString(entity, charset);
                }
                EntityUtils.consume(entity);
                if (status == 200) {
                    logger.info("url: " + uri + ",data: " + result);
                    return result;
                } else {
                    throw new ClientProtocolException("Unexpected response status: " + status + ",msg:" + result);
                }
            }

        };
        String result = "";
        try {
            result = httpClient.execute(req, responseHandler);
        } finally {
            req.releaseConnection();
        }
        return result;
    }

    public static void getToResponse(String url, final HttpServletResponse resp) throws ClientProtocolException, IOException {
        CloseableHttpClient httpClient = createHttpClient(3, 20, 10);
        try {
            String mime = "application/x-www-form-urlencoded";
            HttpGet httpGet = new HttpGet(url);
            Map<String, String> headers = new HashMap<>();
            headers.put("Accept", mime);
            headers.put("Content-Type", mime + ";UTF-8");
            HttpRequestBase req = configRequest(httpGet, headers);
            ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
                public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {
                    int status = response.getStatusLine().getStatusCode();
                    HttpEntity entity = response.getEntity();
                    String len = response.getFirstHeader("Content-Length").getValue();
                    String nameTmp = response.getFirstHeader("Content-Disposition").getValue();
                    resp.reset();
                    resp.setContentType("application/x-download");
                    resp.addHeader("Content-Disposition", nameTmp);
                    resp.addHeader("Content-Length", len);
                    if (200 != status) {
                        resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    }
                    if (null != entity) {
                        IOUtils.copyLarge(entity.getContent(), resp.getOutputStream());
                    }
                    EntityUtils.consume(entity);
                    return "";
                }

            };
            httpClient.execute(req, responseHandler);
        } finally {
            httpClient.close();
        }
    }

    public static String postMethod(HttpClient httpClient, String url, String data, String... mimeArg)
            throws IOException {
        String mime = null != mimeArg && mimeArg.length > 0 ? mimeArg[0] : "application/json";
        HttpPost httpPost = new HttpPost(url);
        HttpEntity reqEntity = new StringEntity(data, ContentType.create(mime, "UTF-8"));
        httpPost.setEntity(reqEntity);
        Map<String, String> headers = new HashMap<>();
        headers.put("Accept", mime);
        headers.put("Content-Type", mime + ";UTF-8");
        HttpRequestBase req = configRequest(httpPost, headers);
        return execute(httpClient, req);
    }

    public static String postMethod(String url, String data) throws IOException {
        CloseableHttpClient httpClient = createHttpClient(3, 20, 10);
        try {
            return postMethod(httpClient, url, data);
        } finally {
            httpClient.close();
        }
    }

    public static String putMethod(HttpClient httpClient, String url, String data, String... mimeArg)
            throws IOException {
        String mime = null != mimeArg && mimeArg.length > 0 ? mimeArg[0] : "application/json";
        HttpPut httpPost = new HttpPut(url);
        HttpEntity reqEntity = new StringEntity(data, ContentType.create(mime, "UTF-8"));
        httpPost.setEntity(reqEntity);
        Map<String, String> headers = new HashMap<>();
        headers.put("Accept", mime);
        headers.put("Content-Type", mime + ";UTF-8");
        HttpRequestBase req = configRequest(httpPost, headers);
        return execute(httpClient, req);
    }

    public static String putMethod(String url, String data) throws IOException {
        CloseableHttpClient httpClient = createHttpClient(3, 20, 10);
        try {
            return putMethod(httpClient, url, data);
        } finally {
            httpClient.close();
        }
    }

    public static String getMethod(HttpClient httpClient, String url, String... mimeArg) throws IOException {
        String mime = null != mimeArg && mimeArg.length > 0 ? mimeArg[0] : "";
        HttpGet httpGet = new HttpGet(url);
        Map<String, String> headers = new HashMap<>();
        headers.put("Accept", mime);
        headers.put("Content-Type", mime + ";UTF-8");
        HttpRequestBase req = configRequest(httpGet, headers);
        return execute(httpClient, req);
    }

    public static String getMethod(String url) throws IOException {
        CloseableHttpClient httpClient = createHttpClient(3, 20, 10);
        try {
            return getMethod(httpClient, url);
        } finally {
            httpClient.close();
        }
    }

    public static String deleteMethod(HttpClient httpClient, String url, String... mimeArg) throws IOException {
        String mime = null != mimeArg && mimeArg.length > 0 ? mimeArg[0] : "";
        HttpDelete httpGet = new HttpDelete(url);
        Map<String, String> headers = new HashMap<>();
        headers.put("Accept", mime);
        headers.put("Content-Type", mime + ";UTF-8");
        HttpRequestBase req = configRequest(httpGet, headers);
        return execute(httpClient, req);
    }

    public static String deleteMethod(String url) throws IOException {
        CloseableHttpClient httpClient = createHttpClient(3, 20, 10);
        try {
            return deleteMethod(httpClient, url);
        } finally {
            httpClient.close();
        }
    }

}
